package server;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.List;

/** 
 * Dieser Thread wird vom Server geöffnet, sobald eine eingehende TCP-Verbindung
 * registriert wurde. 
 * Der UserHandler wird mit dem Socket initialisiert. Er behandlet alle Anfragen,
 * die der Client an den Server stellt.
 * 
 * Sollte eine Exception auftreten, so wird dieser Thread beendet und der Client,
 * falls er angemeldet war, abgemeldet.
 */
public class UserHandler extends Thread {
    private Socket socket;
    private Server server;
    private User user;
    private boolean sessionActive = true;
    private boolean loggedIn = false;
    
    private BufferedReader iStream;
    private DataOutputStream oStream;

    public UserHandler(Socket sock, Server server) throws IOException {
        this.socket = sock;
        this.server = server;
        iStream = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        oStream = new DataOutputStream(socket.getOutputStream());
    }

    public void run() {
        try {

            while (sessionActive) {
                String[] request = tcpRecv().split(" ");
                String command = request[0].toUpperCase();
                
                if (!loggedIn) {
                    switch(command) {
                        case "NEW": //Client will sich anmelden
                            NewCommand(request);
                            break;
                        default: //Unbekannter Befehl
                            sendError("you are not logged in, command '" + command + "' is not allowed!");
                            break;
                    }
                } else {
                    switch(command) {
                        case "INFO": //Client will die Liste aller angemeldeten clients seheen
                            InfoCommand();
                            break;
                        case "BYE": //Client will sich abmelden
                            sessionActive = false;
                            server.removeUser(user);
                            tcpSend("BYE");
                            break;
                        default: //Unbekannter Befehl eingegeben
                            server.removeUser(user);
                            sendError("you are logged in, command '" + command + "' is not allowed!");
                            break;
                    }
                }
            }
            
            socket.close();
        } catch (IOException e) {
            System.err.println(e.toString() + " received, closing connection");
            if (loggedIn)
                server.removeUser(user);
        }
    }
    
    /**
     * Implementierung des NEW <name> Befehls. 
     * @param request die nach Leerzeichen aufgesplittete Anfrage des Clients.
     * @throws IOException 
     */
    private void NewCommand(String[] request) throws IOException {
        String hostname = socket.getInetAddress().getHostName();
        if (request.length == 2) {
            String username = request[1];
            if (username.length() <= 20) { //Namenslänge prüfen
                //synchronized()
                if (!server.containsUser(username)) {
                    if (username.matches("[A-Za-z0-9]*")) {
                        user = server.addUser(new User(socket, hostname, username));
                        loggedIn = true;
                        tcpSend("OK");
                    } else {
                        sendError("username mustn't contain special characters");
                    }
                } else {
                    sendError("username '" + username + "'is already in use");
                }
            } else {
                sendError("username exceeds 20 characters");
            }
        } else {
            sendError("NEW expects one parameter");
        }
    }
    
    /** 
     * Implementierung des INFO Befehls
     */ 
    private void InfoCommand() throws IOException {
        StringBuilder response = new StringBuilder(1024);
        List<User> userList = server.userList();
        response.append("LIST ").append(userList.size());
        for(User u : userList) {
            response.append(" ").append(u.hostname).append(" ").append(u.username);
        }
        tcpSend(response.toString());
    }
    
    /**
     * Emfangsmethode für eingehende TCP-Anfragen
     * @return ein String, welcher die Nachricht enthält
     * @throws IOException 
     */
    private String tcpRecv() throws IOException {
        return iStream.readLine();
    }
    
    /**
     * Methode zum Senden von Antworten an den Client. Die Antworten werden
     * protokollgemäß automatisch mit einem '\n' abgeschlossen.
     * @param message der Inhalt der Antwort
     * @throws IOException 
     */
    private void tcpSend(String message) throws IOException {
        oStream.writeBytes(message + "\n");
    } 
    
    /**
     * Da häufig mit einem ERROR <?> geantwortet wird, wurde diese Nachricht in eine 
     * eigene Methode gekapselt.
     * @param reason der Fehlergrund
     * @throws IOException 
     */
    private void sendError(String reason) throws IOException {
        sessionActive = false;
        tcpSend("ERROR " + reason);
    }
        
}
